unit ConfigFrm;

interface

uses
  ColorSchemes, Settings,
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
  System.ImageList, Vcl.ImgList, PngImageList, System.Actions, Vcl.ActnList,
  Vcl.Buttons, Vcl.ExtCtrls, VirtualTrees, PngBitBtn, Vcl.StdActns, EditFra;

type
  TConfigForm = class(TForm)
    PageControl1: TPageControl;
    OkBtn: TButton;
    CancelBtn: TButton;
    GeneralTab: TTabSheet;
    ViewTab: TTabSheet;
    SchemeTab: TTabSheet;
    PngImageList1: TPngImageList;
    ActionList1: TActionList;
    SchemeNew: TAction;
    SchemeDelete: TAction;
    SchemeCopy: TAction;
    SchemeColorGroup: TGroupBox;
    SchemeRuleGroup: TGroupBox;
    SchemeExpLabel: TLabel;
    SchemeExpEdit: TEdit;
    SchemeCaseCheck: TCheckBox;
    SchemeInvertCheck: TCheckBox;
    Label2: TLabel;
    SchemeColorList: TVirtualStringTree;
    SchemeBackColorBox: TColorBox;
    SchemeForeColorBox: TColorBox;
    Label3: TLabel;
    Button5: TButton;
    Button6: TButton;
    SchemeColorListLabel: TLabel;
    SchemeColorMoveUp: TAction;
    SchemeColorMoveDown: TAction;
    SchemeRename: TAction;
    SchemeReset: TAction;
    Button7: TButton;
    SchemeListPanel: TPanel;
    Label1: TLabel;
    SchemeCombo: TComboBox;
    Button1: TPngBitBtn;
    Button2: TPngBitBtn;
    Button3: TPngBitBtn;
    Button4: TPngBitBtn;
    Label4: TLabel;
    Edit1: TEdit;
    Button8: TButton;
    Label5: TLabel;
    Edit2: TEdit;
    Label6: TLabel;
    Edit3: TEdit;
    ViewSetFont: TFontEdit;
    SampleEdit: TEditFrame;
    ColorTimer: TTimer;
    procedure SchemeNewExecute(Sender: TObject);
    procedure SchemeDeleteExecute(Sender: TObject);
    procedure SchemeCopyExecute(Sender: TObject);
    procedure SchemeRenameExecute(Sender: TObject);
    procedure SchemeRenameUpdate(Sender: TObject);
    procedure SchemeDeleteUpdate(Sender: TObject);
    procedure SchemeComboChange(Sender: TObject);
    procedure SchemeColorListFocusChanged(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex);
    procedure SchemeColorListGetText(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: string);
    procedure SchemeColorListBeforeItemErase(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect;
      var ItemColor: TColor; var EraseAction: TItemEraseAction);
    procedure SchemeColorListDrawText(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
      const Text: string; const CellRect: TRect; var DefaultDraw: Boolean);
    procedure SchemeColorListInitNode(Sender: TBaseVirtualTree; ParentNode,
      Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
    procedure SchemeColorListChecked(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    procedure SchemeColorMoveDownExecute(Sender: TObject);
    procedure SchemeColorMoveDownUpdate(Sender: TObject);
    procedure SchemeColorMoveUpUpdate(Sender: TObject);
    procedure ColorBoxGetColors(Sender: TCustomColorBox; Items: TStrings);
    procedure SchemeResetExecute(Sender: TObject);
    procedure SchemeColorMoveUpExecute(Sender: TObject);
    procedure SchemeColorListFocusChanging(Sender: TBaseVirtualTree; OldNode,
      NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
      var Allowed: Boolean);
    procedure SchemeExpEditChange(Sender: TObject);
    procedure ColorTimerTimer(Sender: TObject);
    procedure ViewSetFontBeforeExecute(Sender: TObject);
    procedure ViewSetFontAccept(Sender: TObject);
  private
    { Private declarations }
    FSettings: TSettings;
    FSchemes: TColorSchemeCollection;
    FEditSchemeID: string;
    procedure CreateObjects;
    procedure LoadSettings;
    procedure SaveSettings;
    procedure SelectedSchemeChanged;
    procedure SelectedColorChanged;
    procedure SaveColorItem;

    function SelectedScheme: TColorScheme;
    function SelectedColor: TColorItem;
  public
    { Public declarations }
    class function Configuration(AOwner: TComponent; const ASchemeID: string = ''): Boolean;
  end;

implementation

{$R *.dfm}

uses
  Constants, Helpers;

{ TConfigForm }

class function TConfigForm.Configuration(AOwner: TComponent;
  const ASchemeID: string): Boolean;
begin
  with TConfigForm.Create(AOwner) do
  try
    FEditSchemeID := ASchemeID;
    CreateObjects;
    LoadSettings;
    Result := ShowModal = mrOk;
    if Result then SaveSettings;    
  finally
    Free;
  end;
end;

procedure TConfigForm.CreateObjects;
begin
  if FEditSchemeID.IsEmpty then
  begin
    FSettings := TSettings.Create;
    FSettings.Assign(AppSettings);
  end
  else
    FSettings := nil;

  FSchemes := TColorSchemeCollection.Create;
  if FEditSchemeID.IsEmpty then
    FSchemes.Assign(AppColorSchemes)
  else // edit color item only
    FSchemes.Add.Assign(AppColorSchemes.Find(FEditSchemeID));
end;

procedure TConfigForm.SaveSettings;
begin
  if GeneralTab.TabVisible then
  begin

  end;

  if ViewTab.TabVisible then
  begin
    FSettings.ViewFont.Assign(SampleEdit.Font);
  end;

  SaveColorItem;
  if FEditSchemeID.IsEmpty then
  begin
    AppSettings.Assign(FSettings);
    FSettings.Free;

    AppColorSchemes.Assign(FSchemes);
    FSchemes.Free;
  end
  else
  begin
    AppColorSchemes.Find(FEditSchemeID).Assign(SelectedScheme);
    FSchemes.Free;
  end;
end;

procedure TConfigForm.LoadSettings;
begin
  if not FEditSchemeID.IsEmpty then
  begin
    GeneralTab.TabVisible := False;
    ViewTab.TabVisible := False;
    SchemeListPanel.Visible := False;
  end
  else
    SchemeTab.PageControl.ActivePage := GeneralTab;

  if GeneralTab.TabVisible then
  begin

  end;

  if ViewTab.TabVisible then
  begin
    SampleEdit.ApplySettings(FSettings);
    SampleEdit.ApplyColorScheme(TColorScheme.DefaultSchemeID);
  end;

  // show color schemes
  SchemeCombo.Items.BeginUpdate;
  try
    SchemeCombo.Items.Clear;
    for var ItemIndex := 0 to FSchemes.Count - 1 do
    begin
      var Scheme := FSchemes[ItemIndex];
      Scheme.Modified := False;
      SchemeCombo.Items.AddObject(Scheme.DisplayName, Scheme);
    end;

    SchemeCombo.ItemIndex := 0;
  finally
    SchemeCombo.Items.EndUpdate;
    SelectedSchemeChanged;
  end;
end;

procedure TConfigForm.ViewSetFontBeforeExecute(Sender: TObject);
begin
  ViewSetFont.Dialog.Font := SampleEdit.Font;
end;

procedure TConfigForm.ViewSetFontAccept(Sender: TObject);
begin
  SampleEdit.Font.Assign(ViewSetFont.Dialog.Font);
end;

function TConfigForm.SelectedScheme: TColorScheme;
begin
  Result := TColorScheme(SchemeCombo.Items.Objects[SchemeCombo.ItemIndex]);
end;

function TConfigForm.SelectedColor: TColorItem;
begin
  Result := SelectedScheme.Colors[SchemeColorList.FocusedNode^.Index];
end;

procedure TConfigForm.SelectedSchemeChanged;
begin
  SchemeColorList.Clear;
  SchemeColorList.RootNodeCount := SelectedScheme.Colors.Count;
  SchemeColorList.FocusedNode := SchemeColorList.GetFirst;
end;

procedure TConfigForm.SelectedColorChanged;
begin
  with SelectedColor do
  begin
    SchemeExpEdit.Text := Pattern;
    SchemeCaseCheck.Checked := CaseSensitive;
    SchemeInvertCheck.Checked := InvertMatch;
    SchemeBackColorBox.Selected := BackColor;
    SchemeForeColorBox.Selected := ForeColor;
  end;
end;

procedure TConfigForm.SaveColorItem;
begin
  ColorTimer.Enabled := False;
  // save current state
  with SelectedColor do
  begin
    Pattern := SchemeExpEdit.Text;
    CaseSensitive := SchemeCaseCheck.Checked;
    InvertMatch := SchemeInvertCheck.Checked;
    BackColor := SchemeBackColorBox.Selected;
    ForeColor := SchemeForeColorBox.Selected;
  end;
  SchemeColorList.Invalidate;
end;

procedure TConfigForm.SchemeComboChange(Sender: TObject);
begin
  SelectedSchemeChanged;
end;

procedure TConfigForm.SchemeExpEditChange(Sender: TObject);
begin
  ColorTimer.Enabled := False;
  ColorTimer.Enabled := True;
end;

procedure TConfigForm.ColorTimerTimer(Sender: TObject);
begin
  SaveColorItem;
end;

procedure TConfigForm.SchemeColorListFocusChanged(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex);
begin
  SelectedColorChanged;
end;

procedure TConfigForm.SchemeColorListFocusChanging(Sender: TBaseVirtualTree;
  OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
  var Allowed: Boolean);
begin
  if Assigned(OldNode) then SaveColorItem;
end;

procedure TConfigForm.SchemeColorListInitNode(Sender: TBaseVirtualTree;
  ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  with SelectedScheme.Colors[Node^.Index] do
  begin
    Node^.CheckType := ctCheckBox;
    if Active then Node^.CheckState := csCheckedNormal;
  end;
end;

procedure TConfigForm.SchemeColorListGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
begin
  with SelectedScheme.Colors[Node^.Index] do
    if TextType = ttNormal then
      CellText := Format('#%d: %s', [Node^.Index + 1, Pattern]);
end;

procedure TConfigForm.SchemeColorListBeforeItemErase(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect;
  var ItemColor: TColor; var EraseAction: TItemEraseAction);
begin
  with SelectedScheme.Colors[Node^.Index] do
    if BackColor <> clDefault then
    begin
      EraseAction := eaColor;
      ItemColor := BackColor;
    end;
end;

procedure TConfigForm.SchemeColorListDrawText(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  const Text: string; const CellRect: TRect; var DefaultDraw: Boolean);
begin
  with SelectedScheme.Colors[Node^.Index] do
    if ForeColor <> clDefault then
      TargetCanvas.Font.Color := ForeColor;
end;

procedure TConfigForm.SchemeColorListChecked(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
begin
  if not (vsInitializing in Node^.States) then
  begin
    SchemeColorList.FocusedNode := Node;
    with SelectedScheme.Colors[Node^.Index] do
      Active := Sender.CheckState[Node] = csCheckedNormal;
  end;
end;

procedure TConfigForm.ColorBoxGetColors(Sender: TCustomColorBox;
  Items: TStrings);
begin
  Items.InsertObject(1, SDefaultColorName, TObject(clDefault));
end;

procedure TConfigForm.SchemeResetExecute(Sender: TObject);
begin
  if AppMsgBox(SConfirmResetScheme, STitleConfirm,
    MB_ICONQUESTION or MB_YESNO) = IDNO then Exit;

  with SelectedScheme do
  begin
    var Source := AppColorSchemes.Find(SchemeID);
    if Assigned(Source) then Assign(Source) else Reset;

    SelectedSchemeChanged;
  end;
end;

procedure TConfigForm.SchemeColorMoveUpExecute(Sender: TObject);
begin
  with SchemeColorList do
    SelectedScheme.Move(FocusedNode^.Index, FocusedNode^.Index - 1);
end;

procedure TConfigForm.SchemeColorMoveUpUpdate(Sender: TObject);
begin
  with SchemeColorList do
    SchemeColorMoveUp.Enabled := FocusedNode^.Index > 0;
end;

procedure TConfigForm.SchemeColorMoveDownExecute(Sender: TObject);
begin
  with SchemeColorList do
    SelectedScheme.Move(FocusedNode^.Index, FocusedNode^.Index + 1);
end;

procedure TConfigForm.SchemeColorMoveDownUpdate(Sender: TObject);
begin
  with SchemeColorList do
    SchemeColorMoveDown.Enabled := FocusedNode^.Index < RootNodeCount - 1;
end;

procedure TConfigForm.SchemeNewExecute(Sender: TObject);
begin
  var Scheme := FSchemes.Add;
  Scheme.AutoFill;
  Scheme.SchemeName := FSchemes.GetUniqueName(Scheme, SDefaultSchemeName);
  SchemeCombo.ItemIndex := SchemeCombo.Items.AddObject(Scheme.DisplayName, Scheme);
  SelectedSchemeChanged;
end;

procedure TConfigForm.SchemeCopyExecute(Sender: TObject);
begin
  var Scheme := FSchemes.Add;
  Scheme.Assign(SelectedScheme);
  Scheme.NewID;
  Scheme.SchemeName := FSchemes.GetUniqueName(Scheme, SDefaultSchemeCopyName);
  SchemeCombo.ItemIndex := SchemeCombo.Items.AddObject(Scheme.DisplayName, Scheme);
  SelectedSchemeChanged;
end;

procedure TConfigForm.SchemeDeleteExecute(Sender: TObject);
begin
  if AppMsgBox(SConfirmDeleteScheme, STitleConfirm,
    MB_ICONQUESTION or MB_YESNO) = IDNO then Exit;

  var Index := SchemeCombo.ItemIndex;
  SchemeCombo.ItemIndex := Index - 1;
  SchemeCombo.Items.Delete(Index);
  FSchemes.Delete(Index);
  SelectedSchemeChanged;
end;

procedure TConfigForm.SchemeDeleteUpdate(Sender: TObject);
begin
  SchemeDelete.Enabled := not SelectedScheme.IsDefault;
end;

procedure TConfigForm.SchemeRenameExecute(Sender: TObject);
begin
  var Scheme := SelectedScheme;
  var NewName := Scheme.SchemeName;
  if not InputQuery(STitleRename, SQueryNewSchemeName, NewName) then Exit;

  NewName := Trim(NewName);
  if NewName.IsEmpty then
  begin
    AppMsgBox(SColorSchemeNameEmpty, STitleError, MB_ICONSTOP);
    Exit;
  end;
  if not FSchemes.ValidateName(Scheme, NewName) then
  begin
    AppMsgBox(Format(SColorSchemeNameExists, [NewName]), STitleError, MB_ICONSTOP);
    Exit;
  end;

  var Index := SchemeCombo.ItemIndex;
  Scheme.SchemeName := NewName;
  SchemeCombo.Items[Index] := NewName;
  SchemeCombo.ItemIndex := Index;
end;

procedure TConfigForm.SchemeRenameUpdate(Sender: TObject);
begin
  SchemeRename.Enabled := not SelectedScheme.IsDefault;
end;

end.
